package com.excel.validators.core.extension;

import java.util.LinkedHashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import com.excel.validators.core.extension.annotation.ExcelNode;
import com.excel.validators.core.extension.annotation.Validated;

/**
 * @author wangxiaoliang10
 *
 * 2017年8月5日
 */
public class CoreProcess extends AbstractProcessor {

		private Types typeUtils;
		
		private Elements elementUtils;
		
		private Messager messager;
	
	  @Override 
	  public synchronized void init(ProcessingEnvironment processingEnv) {
		    super.init(processingEnv);
		    typeUtils = processingEnv.getTypeUtils();
		    elementUtils = processingEnv.getElementUtils();
		    messager = processingEnv.getMessager();
	  }
	
	  @Override 
	  public Set<String> getSupportedAnnotationTypes() {
		    Set<String> annotataions = new LinkedHashSet<String>();
		    annotataions.add(ExcelNode.class.getCanonicalName());
		    return annotataions;
	  }
	  
	  @Override 
	  public SourceVersion getSupportedSourceVersion() {
		  	 return SourceVersion.latestSupported();
	  }

	  /**
	   * Checks if the annotated element observes our rules
	 * @param extend 
	   */
	  private void checkValidClass(TypeElement classElement, Class<?> annotation,boolean extend) throws ProcessingException {

	    // Cast to TypeElement, has more type specific methods
	    if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
		      throw new ProcessingException(classElement, "The class %s is not public.",
		          classElement.getQualifiedName().toString());
	    }

	    // Check if it's an abstract class
	    if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
		      throw new ProcessingException(classElement,
		          "The class %s is abstract. You can't annotate abstract classes with @%",
		          classElement.getQualifiedName().toString(), annotation.getSimpleName());
	    }

	    if (classElement.getKind() == ElementKind.INTERFACE) {
		    		// Check interface implemented
	        throw new ProcessingException(classElement,
	            "The class %s annotated with @%s must be not interface",
	            classElement.getQualifiedName().toString(), annotation.getSimpleName());
      }

	    if (extend) {
	        // Check subclassing
	        TypeElement currentClass = classElement;
	        while (true) {
	  	        TypeMirror superClassType = currentClass.getSuperclass();
	  	        if (superClassType.getKind() == TypeKind.NONE) {
		  	          // Basis class (java.lang.Object) reached, so exit
		  	          throw new ProcessingException(classElement,
		  	              "The class %s annotated with @%s must extends from %s",
		  	              classElement.getQualifiedName().toString(), annotation.getSimpleName(),
		  	              annotation.getCanonicalName());
	  	        }
	  	        if (superClassType.toString().startsWith("com.excel.validators.core.decorative.Validator")) {
	  		          // Required super class found
	  		          break;
	  	        }
	  	        // Moving up in inheritance tree
	  	        currentClass = (TypeElement) typeUtils.asElement(superClassType);
	        }
	    }
	    
	    // Check if an empty public constructor is given
	    for (Element enclosed : classElement.getEnclosedElements()) {
		      if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
			        ExecutableElement constructorElement = (ExecutableElement) enclosed;
			        if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
			            .contains(Modifier.PUBLIC)) {
			          // Found an empty constructor
			          return;
			        }
		      }
	    }
	    
	    // No empty constructor found
	    throw new ProcessingException(classElement,
	        "The class %s must provide an public empty default constructor",
	        classElement.getQualifiedName().toString());
	  }
	  
		@Override
		public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 
				if (rebuild()) return false;
			  // Scan classes
				FactoryAnnotated factory = new FactoryAnnotated(elementUtils);
				
				try {
					
						for (Element validator : roundEnv
								.getElementsAnnotatedWith(Validated.class)) {
								// check class with annotated with validated
								TypeElement vtype = (TypeElement) validator;
								checkValidClass(vtype,Validated.class,true);
								factory.addValidatedElement(vtype);
						}

						// excel model scan
						for (Element audit : roundEnv
								.getElementsAnnotatedWith(ExcelNode.class)) {
								// Check if a class has been annotated with @AuditNode
								TypeElement mtype = (TypeElement) audit;
								checkValidClass(mtype,ExcelNode.class,false);
								factory.addModelElement(mtype);
						}

						// TODO core code
						factory.makeInstall();
						
				} catch (Exception e) {
						error(null, e.getMessage());
				}
				
				return false;
		}
	
	  /**
	   * prints an error message
	   * @param the element which has caused the error
	   * @param error message
	   */
	  public void error(Element e, String msg) {
		  	messager.printMessage(Diagnostic.Kind.ERROR, msg, e);
	  }
	  
	  public boolean rebuild() {
		  		return System.getProperty("maven.rebuild") != null;
	  }
	  
}
